using System;
using ActiproSoftware.SyntaxEditor;
using ActiproSoftware.SyntaxEditor.Addons.Dynamic;

namespace RWXMLEdit.Client
{
	/// <summary>
	/// Provides an implementation of a <c>VB.NET</c> syntax language that can perform automatic outlining.
	/// </summary>
	public class VBDotNetDynamicSyntaxLanguage : DynamicOutliningSyntaxLanguage {
		
		/////////////////////////////////////////////////////////////////////////////////////////////////////
		// OBJECT
		/////////////////////////////////////////////////////////////////////////////////////////////////////
		
		/// <summary>
		/// This constructor is for designer use only and should never be called by your code.
		/// </summary>
		public VBDotNetDynamicSyntaxLanguage() : base() {}

		/// <summary>
		/// Initializes a new instance of the <c>VBDotNetDynamicSyntaxLanguage</c> class. 
		/// </summary>
		/// <param name="key">The key of the language.</param>
		/// <param name="secure">Whether the language is secure.</param>
		public VBDotNetDynamicSyntaxLanguage(string key, bool secure) : base(key, secure) {}

		/////////////////////////////////////////////////////////////////////////////////////////////////////
		// PUBLIC PROCEDURES
		/////////////////////////////////////////////////////////////////////////////////////////////////////

		/// <summary>
		/// Returns token parsing information for automatic outlining that determines if the current <see cref="IToken"/>
		/// in the <see cref="TokenStream"/> starts or ends an outlining node.
		/// </summary>
		/// <param name="tokenStream">A <see cref="TokenStream"/> that is positioned at the <see cref="IToken"/> requiring outlining data.</param>
		/// <param name="outliningKey">Returns the outlining node key to assign.  A <see langword="null"/> should be returned if the token doesn't start or end a node.</param>
		/// <param name="tokenAction">Returns the <see cref="OutliningNodeAction"/> to take for the token.</param>
		public override void GetTokenOutliningAction(TokenStream tokenStream, ref string outliningKey, ref OutliningNodeAction tokenAction) {
			// Get the token
			IToken token = tokenStream.Peek();

			// See if the token starts or ends an outlining node
			switch (token.Key) {
				case "XMLCommentStartToken":
					outliningKey = "XMLComment";
					tokenAction = OutliningNodeAction.Start;
					break;
				case "XMLCommentEndToken":
					outliningKey = "XMLComment";
					tokenAction = OutliningNodeAction.End;
					break;
				case "SubReservedWordToken":
					outliningKey = "SubBlock";
					tokenAction = OutliningNodeAction.Start;
					break;
				case "EndSubReservedWordToken":
					outliningKey = "SubBlock";
					tokenAction = OutliningNodeAction.End;
					break;
				case "FunctionReservedWordToken":
					outliningKey = "FunctionBlock";
					tokenAction = OutliningNodeAction.Start;
					break;
				case "EndFunctionReservedWordToken":
					outliningKey = "FunctionBlock";
					tokenAction = OutliningNodeAction.End;
					break;
				case "PropertyReservedWordToken":
					outliningKey = "PropertyBlock";
					tokenAction = OutliningNodeAction.Start;
					break;
				case "EndPropertyReservedWordToken":
					outliningKey = "PropertyBlock";
					tokenAction = OutliningNodeAction.End;
					break;
				case "ClassReservedWordToken":
					outliningKey = "ClassBlock";
					tokenAction = OutliningNodeAction.Start;
					break;
				case "EndClassReservedWordToken":
					outliningKey = "ClassBlock";
					tokenAction = OutliningNodeAction.End;
					break;
				case "InterfaceReservedWordToken":
					outliningKey = "InterfaceBlock";
					tokenAction = OutliningNodeAction.Start;
					break;
				case "EndInterfaceReservedWordToken":
					outliningKey = "InterfaceBlock";
					tokenAction = OutliningNodeAction.End;
					break;
				case "EnumReservedWordToken":
					outliningKey = "EnumBlock";
					tokenAction = OutliningNodeAction.Start;
					break;
				case "EndEnumReservedWordToken":
					outliningKey = "EnumBlock";
					tokenAction = OutliningNodeAction.End;
					break;
				case "StructureReservedWordToken":
					outliningKey = "StructureBlock";
					tokenAction = OutliningNodeAction.Start;
					break;
				case "EndStructureReservedWordToken":
					outliningKey = "StructureBlock";
					tokenAction = OutliningNodeAction.End;
					break;
				case "ModuleReservedWordToken":
					outliningKey = "ModuleBlock";
					tokenAction = OutliningNodeAction.Start;
					break;
				case "EndModuleReservedWordToken":
					outliningKey = "ModuleBlock";
					tokenAction = OutliningNodeAction.End;
					break;
				case "NamespaceReservedWordToken":
					outliningKey = "NamespaceBlock";
					tokenAction = OutliningNodeAction.Start;
					break;
				case "EndNamespaceReservedWordToken":
					outliningKey = "NamespaceBlock";
					tokenAction = OutliningNodeAction.End;
					break;
				case "RegionPreProcessorDirectiveStartToken":
					outliningKey = "RegionPreProcessorDirective";
					tokenAction = OutliningNodeAction.Start;
					break;
				case "EndRegionPreProcessorDirectiveStartToken":
					outliningKey = "RegionPreProcessorDirective";
					tokenAction = OutliningNodeAction.End;
					break;
			}
		}
		
		/// <summary>
		/// Resets the <see cref="SyntaxLanguage.LineCommentDelimiter"/> property to its default value.
		/// </summary>
		public override void ResetLineCommentDelimiter() {
			this.LineCommentDelimiter = "'";
		}
		/// <summary>
		/// Indicates whether the <see cref="SyntaxLanguage.LineCommentDelimiter"/> property should be persisted.
		/// </summary>
		/// <returns>
		/// <c>true</c> if the property value has changed from its default; otherwise, <c>false</c>.
		/// </returns>
		public override bool ShouldSerializeLineCommentDelimiter() {
			return (this.LineCommentDelimiter != "'");
		}
		
		/// <summary>
		/// Occurs after automatic outlining is performed on a <see cref="Document"/> that uses this language.
		/// </summary>
		/// <param name="document">The <see cref="Document"/> that is being modified.</param>
		/// <param name="e">A <c>DocumentModificationEventArgs</c> that contains the event data.</param>
		/// <remarks>
		/// A <see cref="DocumentModification"/> may or may not be passed in the event arguments, depending on if the outlining
		/// is performed in the main thread.
		/// </remarks>
		protected override void OnDocumentAutomaticOutliningComplete(Document document, DocumentModificationEventArgs e) {
			// If programmatically setting the text of a document...
			if (e.IsProgrammaticTextReplacement) {
				// Collapse all outlining region nodes
				document.Outlining.RootNode.CollapseDescendants("RegionPreProcessorDirective");
			}
		}
		
		/// <summary>
		/// Occurs after a <see cref="Trigger"/> is activated
		/// for a <see cref="SyntaxEditor"/> that has a <see cref="Document"/> using this language.
		/// </summary>
		/// <param name="syntaxEditor">The <see cref="SyntaxEditor"/> that will raise the event.</param>
		/// <param name="e">An <c>TriggerEventArgs</c> that contains the event data.</param>
		protected override void OnSyntaxEditorTriggerActivated(SyntaxEditor syntaxEditor, TriggerEventArgs e) {
			switch (e.Trigger.Key) {
				case "XMLCommentTagListTrigger": {
					// Get the member list
					IntelliPromptMemberList memberList = syntaxEditor.IntelliPrompt.MemberList;
					memberList.ResetAllowedCharacters();

					// Set IntelliPrompt ImageList
					memberList.ImageList = SyntaxEditor.ReflectionImageList;

					// Add items to the list
					int imageIndex = (int)ActiproSoftware.Products.SyntaxEditor.IconResource.Keyword;
					memberList.Clear();
					memberList.Add(new IntelliPromptMemberListItem("c", imageIndex, "Indicates that text within the tag should be marked as code.  Use &lt;code&gt; to indicate multiple lines as code."));
					memberList.Add(new IntelliPromptMemberListItem("code", imageIndex, "Indicates multiple lines as code. Use &lt;c&gt; to indicate that text within a description should be marked as code."));
					memberList.Add(new IntelliPromptMemberListItem("example", imageIndex, "Specifies an example of how to use a method or other library member."));
					memberList.Add(new IntelliPromptMemberListItem("exception", imageIndex, "Specifies which exceptions a class can throw.", "exception cref=\"", "\""));
					memberList.Add(new IntelliPromptMemberListItem("include", imageIndex, "Refers to comments in another file that describe the types and members in your source code.", "include file='", "' path='[@name=\"\"]'/>"));
					memberList.Add(new IntelliPromptMemberListItem("list", imageIndex, "Provides a container for list items.", "list type=\"", "\""));
					memberList.Add(new IntelliPromptMemberListItem("listheader", imageIndex, "Defines the heading row of either a table or definition list."));
					memberList.Add(new IntelliPromptMemberListItem("item", imageIndex, "Defines an item in a table or definition list."));
					memberList.Add(new IntelliPromptMemberListItem("term", imageIndex, "A term to define, which will be defined in text."));
					memberList.Add(new IntelliPromptMemberListItem("description", imageIndex, "Either an item in a bullet or numbered list or the definition of a term."));
					memberList.Add(new IntelliPromptMemberListItem("para", imageIndex, "Provides a paragraph container."));
					memberList.Add(new IntelliPromptMemberListItem("param", imageIndex, "Describes one of the parameters for the method.", "param name=\"", "\"/>"));
					memberList.Add(new IntelliPromptMemberListItem("paramref", imageIndex, "Indicates that a word is a parameter.", "paramref name=\"", "\"/>"));
					memberList.Add(new IntelliPromptMemberListItem("permission", imageIndex, "Documents the access of a member.", "permission cref=\"", "\""));
					memberList.Add(new IntelliPromptMemberListItem("remarks", imageIndex, "Specifies overview information about a class or other type."));
					memberList.Add(new IntelliPromptMemberListItem("returns", imageIndex, "Describes the return value for a method declaration."));
					memberList.Add(new IntelliPromptMemberListItem("see", imageIndex, "Specifies a link from within text.", "see cref=\"", "\"/>"));
					memberList.Add(new IntelliPromptMemberListItem("seealso", imageIndex, "Specifies the text that you might want to appear in a See Also section.", "seealso cref=\"", "\"/>"));
					memberList.Add(new IntelliPromptMemberListItem("summary", imageIndex, "Describes a member for a type."));
					memberList.Add(new IntelliPromptMemberListItem("value", imageIndex, "Describes the value for a property declaration."));

					// Show the list
					if (memberList.Count > 0)
						memberList.Show();
					break;
				}
			}
		}

		/// <summary>
		/// Allows for setting the collapsed text for the specified <see cref="OutliningNode"/>.
		/// </summary>
		/// <param name="node">The <see cref="OutliningNode"/> that is requesting collapsed text.</param>
		public override void SetOutliningNodeCollapsedText(OutliningNode node) {
			TokenCollection tokens = node.Document.Tokens;
			int tokenIndex = tokens.IndexOf(node.StartOffset);
			string tokenKey = tokens[tokenIndex].Key;
          
			switch (tokenKey) {
				case "XMLCommentStartToken":
					node.CollapsedText = "/**/";
					break;
				case "FunctionReservedWordToken":
				case "PropertyReservedWordToken":
				case "SubReservedWordToken": 
				case "ClassReservedWordToken": 
				case "InterfaceReservedWordToken": 
				case "EnumReservedWordToken": 
				case "StructureReservedWordToken":
				case "ModuleReservedWordToken": 
				case "NamespaceReservedWordToken": {
					string collapsedText = String.Empty;
					while (++tokenIndex < tokens.Count) {
						if (tokens[tokenIndex].IsWhitespace)
							continue;

						switch (tokens[tokenIndex].Key) {
							case "LineTerminatorToken":
							case "OpenParenthesisPatternGroup":
								continue;
							default:
								collapsedText = tokens.Document.GetTokenText(tokens[tokenIndex]);
								break;
						}
						break;
					}

					switch (tokenKey) {
						case "FunctionReservedWordToken":
							node.CollapsedText = String.Format("Function {0}()", collapsedText.Trim());
							break;
						case "PropertyReservedWordToken":
							node.CollapsedText = String.Format("Property {0}()", collapsedText.Trim());
							break;
						case "SubReservedWordToken":
							node.CollapsedText = String.Format("Sub {0}()", collapsedText.Trim());
							break;
						case "ClassReservedWordToken":
							node.CollapsedText = String.Format("Class {0}", collapsedText.Trim());
							break;
						case "InterfaceReservedWordToken":
							node.CollapsedText = String.Format("Interface {0}", collapsedText.Trim());
							break;
						case "EnumReservedWordToken":
							node.CollapsedText = String.Format("Enum {0}", collapsedText.Trim());
							break;
						case "StructureReservedWordToken":
							node.CollapsedText = String.Format("Structure {0}", collapsedText.Trim());
							break;
						case "ModuleReservedWordToken":
							node.CollapsedText = String.Format("Module {0}", collapsedText.Trim());
							break;
						case "NamespaceReservedWordToken":
							node.CollapsedText = String.Format("Namespace {0}", collapsedText.Trim());
							break;
					}
					break;
				}
				case "RegionPreProcessorDirectiveStartToken": {
					string collapsedText = String.Empty;
					while (++tokenIndex < tokens.Count) {
						if (tokens[tokenIndex].Key == "PreProcessorDirectiveEndToken")
							break;

						collapsedText += tokens.Document.GetTokenText(tokens[tokenIndex]);
					}
					collapsedText = collapsedText.Trim();
					if (collapsedText.StartsWith("\""))
						collapsedText = collapsedText.Substring(1);
					if (collapsedText.EndsWith("\""))
						collapsedText = collapsedText.Substring(0, collapsedText.Length - 1);
					if (collapsedText.Length == 0)
						collapsedText = "...";
					node.CollapsedText = collapsedText.Trim();
					break;
				}
			}
		}

	}
}
